#include "OpenglAppFramework/inc/OpenglEnv.h"
#include "OpenglAppFramework/inc/GameObject.h"
#include "OpenglAppFramework/inc/PlugIn/LightSource.h"
#include "OpenglAppFramework/inc/Shader.h"
#include "OpenglAppFramework/inc/Config.h"
#include "OpenglAppFramework/inc/Utils.h"
#include "OpenglAppFramework/inc/Scene.h"
#include "OpenglAppFramework/inc/SceneConfig.h"
#include "OpenglAppFramework/inc/glm/glm.hpp"
#include "OpenglAppFramework/inc/glm/gtc/matrix_transform.hpp"
#include "OpenglAppFramework/inc/glm/gtc/type_ptr.hpp"

#define SHADOW_MAP_WIDHT    (800)
#define SHADOW_MAP_HEIGHT   (600)

int LightSourceType2Int(LightSourceType type) {
    switch(type) {
    case LightSourceType::Parallel:    return 1;
    case LightSourceType::Spot:        return 2;
    case LightSourceType::Point:       return 3;
    default:                     return 0;
    }
    return 0;
}

LightSource::LightSource(const std::string& name, LightSourceType type):
    PlugIn(name, PlugInType::ParallelLight), // !!!如果新增光源类型的话，需要增加：LightSourceType2PlugInType()
    m_scam(name + "__scam") {
    m_light_type = type;
    m_ambient = m_diffuse = m_specular = m_direction = glm::vec3(0.0f);
    m_half_angle = 0.0f;
    m_is_shadow_enabled = false;
    m_shadow_fbo_id = 0;
    m_shadow_tex_id = 0;
}

LightSource::~LightSource() {}

// LightSource& LightSource::setLightColor(const glm::vec3& ambinet, const glm::vec3& diffuse, const glm::vec3& specular) {
//     m_ambient = ambinet;
//     m_diffuse = diffuse;
//     m_specular = specular;
//     return *this;
// }

// LightSource& LightSource::setDirection(const glm::vec3& dir) {
//     m_direction = dir;
//     return *this;
// }

// LightSource& LightSource::setHalfAngle(float half_angle) {
//     m_half_angle = half_angle;
//     return *this;
// }

Shader* LightSource::getShadowShader() {
    return &m_shadow_shader;
}

unsigned int LightSource::getShadowTextureId() {
    return m_shadow_tex_id;
}

void LightSource::enableShadow(bool is_enable) {
    m_is_shadow_enabled = is_enable;
}

void LightSource::setDirectionNormalized(const glm::vec3& direction) {
    m_direction = direction;
    m_scam.setFrontVec(m_direction);
}

bool LightSource::isShadowEnabled() const {
    return m_is_shadow_enabled;
}

int LightSource::init() {
    // 不开启阴影时，不需要init
    if(m_is_shadow_enabled == false) {
        return 0;
    }

    m_scam.setGameObject(m_obj);
    if(m_scam.init() != 0) {
        return -1;
    }

    // 生成阴影专用Shader
    std::string vert_file = OpenglAppConfig::shader_dir + "/shadow.vert";
    std::string frag_file = OpenglAppConfig::shader_dir + "/shadow.frag";
    NE0_PRN1(m_shadow_shader.createAndBuild(vert_file.c_str(), frag_file.c_str()));
    m_shadow_shader.use();CHECK_OPENGL(__FILE__,__LINE__);

    // create depth texture
    glGenTextures(1, &m_shadow_tex_id);
    glBindTexture(GL_TEXTURE_2D, m_shadow_tex_id);CHECK_OPENGL(__FILE__,__LINE__);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_MAP_WIDHT, SHADOW_MAP_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);CHECK_OPENGL(__FILE__,__LINE__);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);CHECK_OPENGL(__FILE__,__LINE__);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);CHECK_OPENGL(__FILE__,__LINE__);
    // attach depth texture as FBO's depth buffer
    glGenFramebuffers(1, &m_shadow_fbo_id);
    glBindFramebuffer(GL_FRAMEBUFFER, m_shadow_fbo_id);CHECK_OPENGL(__FILE__,__LINE__);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadow_tex_id, 0);CHECK_OPENGL(__FILE__,__LINE__);
    glDrawBuffer(GL_NONE);
    glReadBuffer(GL_NONE);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);CHECK_OPENGL(__FILE__,__LINE__);

    m_shadow_shader.setVec2("use_nouse_instancing", glm::vec2(0, 1));
    if(shadowShaderInit() != 0) {
        return -1;
    }
    return 0;
}

int LightSource::shadowShaderInit() {
    return 0;
}

void LightSource::destroy() {
    m_shadow_shader.destroy();
}

void LightSource::uploadShaderData(Shader* s) {
    s->setInt("light.type", LightSourceType2Int(m_light_type));
    s->setBool("light.has_shadow", m_is_shadow_enabled);
    s->setVec3("light.ambient_color", m_ambient);
    s->setVec3("light.diffuse_color", m_diffuse);
    s->setVec3("light.specular_color", m_specular);
    s->setVec3("light.location", location());
    s->setVec3("light.direction", m_direction);
    s->setFloat("light.half_angle", m_half_angle);
    s->setMat4("scam_proj_mat", m_scam.getProjMat());
    s->setMat4("scam_view_mat", m_scam.getViewMat());
    s->setInt("light.shdadow_map_unit_id", 0);
}

void LightSource::uploadShadowShaderData(Shader* s) {
    s->setMat4("scam_view_mat", m_scam.getViewMat());
    // std::cout<<"scam_view_mat"<<std::endl<<mat2str(m_scam.getViewMat())<<std::endl;
}

const glm::vec3& LightSource::location() const {
    return m_obj->transformInfo().location;
}

uint32_t LightSource::getFrameBufferId() const {
    return m_shadow_fbo_id;
}

uint32_t LightSource::getShadowTextureId() const {
    return m_shadow_tex_id;
}

ParallelLightSource::ParallelLightSource(const std::string& name):
    LightSource(name, LightSourceType::Parallel) {}

ParallelLightSource::~ParallelLightSource() {}

int ParallelLightSource::setConfigData(const nlohmann::json& data) {
    glm::vec3 light_color, env_diff_spec;
    json2Vec3(data["light_color"], light_color);
    json2Vec3(data["env_diff_spec"], env_diff_spec);
    m_ambient = light_color * env_diff_spec[0];
    m_diffuse = light_color * env_diff_spec[1];
    m_specular = light_color * env_diff_spec[2];
    json2Vec3(data["direction"], m_direction);
    if(data.find("enable_shadow") != data.end()) {
        m_is_shadow_enabled = data["enable_shadow"].get<bool>();
        if(m_scam.setConfigData(data["scam_config"]) != 0) {
            return -1;
        }
        m_scam.setFrontVec(m_direction);
    }
    return 0;
}

int ParallelLightSource::shadowShaderInit() {
    m_shadow_shader.setMat4("scam_proj_mat", m_scam.getProjMat());CHECK_OPENGL(__FILE__,__LINE__);
    m_shadow_shader.setMat4("scam_view_mat", m_scam.getViewMat());CHECK_OPENGL(__FILE__,__LINE__);

    return 0;
}

#define DFT_CAM_SPEED   (0.03f)
void ParallelLightSource::routine() {
    if(m_is_shadow_enabled == true) {
        m_scam.routine();
    }
}
